package com.jsyso.jsyso.db;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;

import com.jsyso.jsyso.exception.DaoException;
import com.jsyso.jsyso.lang.JStringUtils;
import com.jsyso.jsyso.spring.Springs;
import com.jsyso.jsyso.spring.jdbc.JsMapRowMapper;
import com.jsyso.jsyso.util.Arrays;
import com.jsyso.jsyso.util.Filter;
import com.jsyso.jsyso.util.JsMap;
import com.jsyso.jsyso.util.Maps;

/**
 * Dao操作类
 * 
 * @author janjan, xujian_jason@163.com
 *
 */
public class Dao implements Serializable {
	private static final long serialVersionUID = 1L;
	private static final Logger logger = LoggerFactory.getLogger(Dao.class);
	private static final String DB_MAIN = "mainDB";
	private static final Map<String, Dao> daos = new HashMap<String, Dao>();

	// 当前Db
	private Db db;
	// 参数
	private Options options;
	// 主键
	private String[] pks;

	/**
	 * 创建Dao对象
	 * 
	 * @param tableName
	 *            表名
	 * @return
	 */
	public static Dao get(String tables, String... pks) {
		if (StringUtils.isBlank(tables)) {
			return new Dao();
		}
		Dao dao = daos.get(tables);
		if (dao == null) {
			dao = new Dao().db(DB_MAIN);
			daos.put(tables, dao);
		}
		dao.options = Options.create();
		dao.table(tables);
		dao.pks(pks);
		return dao;
	}

	private Dao() {
	}

	/**
	 * 获得db
	 * 
	 * @param dbId
	 * @return
	 */
	private Db pGetDb(String dbId) {
		// 初始化默认Db
		Db mainDb = null;
		try {
			mainDb = Dao.DB_MAIN.equalsIgnoreCase(dbId) || StringUtils.isBlank(dbId) ? Springs.getBean(Db.class)
					: Springs.getBean(dbId, Db.class);
		} catch (Exception e) {
			logger.warn("[dao create error] dbId = " + dbId + ", getBean fail");
		}
		if(mainDb == null) {
			try {
				mainDb = Springs.getBean(Dao.DB_MAIN, Db.class);
			} catch (Exception e) {
				logger.warn("[dao create error] dbId = " + dbId + ", again getBean fail");
			}
		}
		if(mainDb == null) 
			throw new DaoException("[dao create error] get db fail, use [db(\"dbName\")] to specify databases");
		return mainDb;
			
	}

	/**
	 * 获得读库jdbc操作对象
	 * 
	 * @return
	 */
	private NamedParameterJdbcOperations pSlaveJdbc() {
		if (getDb().isRwSeparate()) {
			NamedParameterJdbcOperations[] slaves = getDb().getSlaves();
//			return slaves[((int) (Math.random() * slaves.length))];
			return Arrays.getRandom(slaves, null);
		} else {
			return getDb().getMaster();
		}
	}

	/**
	 * 获取当前db
	 * 
	 * @return
	 */
	protected Db getDb() {
		return this.db;
	}

	/**
	 * 打印debug日志
	 * 
	 * @param msg
	 */
	private void pDebugLog(String msg) {
		if (getDb().isDebug()) {
			logger.debug(msg);
			System.out.println(msg);
		}
	}

	/**
	 * 切换db对象
	 * 
	 * @param dbId
	 *            spring配置的db id
	 * @return
	 */
	public Dao db(String dbId) {
		this.db = this.pGetDb(dbId);
		return this;
	}

	/**
	 * 设置查询Table
	 * 
	 * @param tableName
	 * @return
	 */
	public Dao table(String tables) {
		if (StringUtils.isNotBlank(tables))
			this.options.setTable(tables);
		return this;
	}

	/**
	 * 设置查询字段
	 * 
	 * @param field
	 * @return
	 */
	public Dao field(String fields) {
		this.options.setField(fields);
		return this;
	}

	/**
	 * 设置查询条件
	 * 
	 * @param where
	 * @return
	 */
	public Dao where(Wheres where) {
		this.options.setWhere(where);
		return this;
	}

	/**
	 * 排序查询
	 * 
	 * @param order
	 * @return
	 */
	public Dao order(String order) {
		this.options.setOrder(order);
		return this;
	}

	/**
	 * 分组查询
	 * 
	 * @param group
	 * @return
	 */
	public Dao group(String group) {
		this.options.setGroup(group);
		return this;
	}

	/**
	 * join查询
	 * 
	 * @param joinSql
	 * @return
	 */
	public Dao join(String joinSql) {
		List<String> joins = this.options.getJoin();
		if (joins == null) {
			joins = new ArrayList<String>();
			this.options.setJoin(joins);
		}
		joins.add(joinSql);
		return this;
	}

	/**
	 * having查询
	 * 
	 * @param having
	 * @return
	 */
	public Dao having(String having) {
		this.options.setHaving(having);
		return this;
	}

	/**
	 * union查询
	 * 
	 * @param sql
	 * @param isAll
	 * @return
	 */
	public Dao union(String sql, boolean isAll) {
		List<Object> objects = this.options.getUnion();
		if (objects == null) {
			objects = new ArrayList<Object>();
			this.options.setUnion(objects);
		}
		objects.add(new Object[] { sql, isAll });
		return this;
	}

	public Dao union(String sql) {
		this.union(sql, false);
		return this;
	}

	/**
	 * distinct查询
	 * 
	 * @param distinct
	 * @return
	 */
	public Dao distinct(boolean distinct) {
		this.options.setDistinct(distinct);
		return this;
	}

	/**
	 * 设置主键
	 * 
	 * @param pks
	 * @return
	 */
	public Dao pks(String... pks) {
		this.pks = pks != null && pks.length > 0 ? pks : new String[] { "id" };
		return this;
	}

	/**
	 * 根据map对象设置where条件
	 * @param params
	 */
	private void pSetWhereByMap(JsMap params) {
		if(params == null || params.isEmpty()) {
			return ;
		}
		Wheres where = this.options.getWhere();
		if (where == null) {
			where = Wheres.create();
			this.where(where);
		}
//		where.setKeys(params.keySet());
		for(String key : params.keySet()) {
			Object value = params.get(key);
			if(value == null) {
				value = "";
//				continue ;
			}
			if(value.getClass().isArray()) {
				where.set(key, value);
			}else {
				where.set(key);
			}
		}
	}
	
	/**
	 * 列表查询
	 * @param params 查询条件
	 * @param rm 映射器
	 * @return
	 */
	public <T> List<T> select(JsMap params, RowMapper<T> rm) {
		this.pSetWhereByMap(params);
		String sql = getDb().getDialect().parseSelectSql(getOptions());
		afterParseSql();
		this.pDebugLog("[jsyso select sql] " + sql);
		return this.pSlaveJdbc().query(sql, params, rm);
	}
	public <T> List<T> select(JsMap params, Class<T> clazz) {
		return this.select(params, new BeanPropertyRowMapper<T>(clazz));
	}
	public <T> List<T> select(Class<T> clazz) {
		return this.select(null, new BeanPropertyRowMapper<T>(clazz));
	}
	public List<JsMap> select(JsMap params) {
		return this.select(params, new JsMapRowMapper());
	}
	public List<JsMap> select() {
		return this.select(null, new JsMapRowMapper());
	}

	/**
	 * 返回一条记录
	 * 
	 * @param params
	 *            查询条件
	 * @param rm
	 *            映射器
	 * @return
	 */
	public <T> T find(JsMap params, RowMapper<T> rm) {
		// 只查找一条记录
		this.limit(1);
		List<T> list = this.select(params, rm);
		return list == null || list.isEmpty() ? null : list.get(0);
	}
	public <T> T find(JsMap params, Class<T> clazz) {
		return this.find(params, new BeanPropertyRowMapper<T>(clazz));
	}
	public <T> T find(Class<T> clazz) {
		return this.find(null, new BeanPropertyRowMapper<T>(clazz));
	}
	public JsMap find(JsMap params) {
		return this.find(params, new JsMapRowMapper());
	}
	public JsMap find() {
		return this.find(null, new JsMapRowMapper());
	}

	/**
	 * Limit查询
	 * 
	 * @param limit
	 * @return
	 */
	public Dao limit(int... limit) {
		this.options.setLimit(limit);
		return this;
	}

	/**
	 * 分页查询
	 * 
	 * @param page
	 *            page[0]=第几页 page[1]=每页多少条记录
	 */
	public Dao page(int... page) {
		this.options.setPage(page);
		return this;
	}
	
	protected Options getOptions() {
		return this.options;
	}
	
	protected void afterParseSql() {
		this.options.setWhere(null);
	}

	/**
	 * count查询
	 * @param params
	 * @return
	 */
	public Long count(JsMap params) {
		this.pSetWhereByMap(params);
		this.field("count(1) AS tmp_count");
		String sql = getDb().getDialect().parseSelectSql(getOptions());
		this.pDebugLog("[dao select count sql] " + sql);
		afterParseSql();
		return pSlaveJdbc().queryForObject(sql, params, Long.class);
	}

	/**
	 * 生成插入sql
	 * 
	 * @param fields
	 * @param isReplace
	 * @return
	 */
	private String pInsertSql(Collection<String> fields, boolean isReplace) {
		StringBuilder value = new StringBuilder();
		for (String key : fields) {
			value.append(",").append(":").append(key);
		}
		Map<String, String> map = new HashMap<String, String>();
		map.put(Dialect.KEY_TABLE, this.options.getTable());
		map.put(Dialect.KEY_INSERT, isReplace ? "REPLACE" : "INSERT");
		map.put(Dialect.KEY_FIELD, StringUtils.join(fields, ","));
		map.put(Dialect.KEY_VALUES, value.deleteCharAt(0).insert(0, '(').append(')').toString());
		return getDb().getDialect().parseInsertSql(map);
	}

	/**
	 * 批量插入数据
	 * 
	 * @param datas
	 * @return
	 */
	public int insertAll(List<JsMap> datas) {
		if (datas == null || datas.isEmpty() || StringUtils.isBlank(this.options.getTable())) {
			logger.warn("[dao insert all error] datas isEmpty || table isEmpty");
			return 0;
		}
		Set<String> fieldSet = datas.get(0).keySet();
		String sql = this.pInsertSql(fieldSet, false);
		this.pDebugLog("[dao insert all sql] " + sql);
		JsMap[] maps = new JsMap[datas.size()];
		int[] results = getDb().getMaster().batchUpdate(sql, datas.toArray(maps));
		int i = 1, length = results == null ? 0 : results.length, count = length > 0 ? results[0] : 0;
		for (; i < length; ++i) {
			count += results[i];
		}
		return count;
	}

	/**
	 * 解析data中是否包含主键，并设置where
	 * 
	 * @param data
	 * @return
	 */
	private boolean pParseDataSetPKWhere(JsMap data) {
		if (data == null || data.isEmpty() || !Maps.containsKeys(data, this.pks)) {
			return false;
		}
		final String[] pks = this.pks;
		this.where(Wheres.create(Maps.filter(data, new Filter<String>() {
			@Override
			public boolean apply(String t) {
				return JStringUtils.equalsAny(t, pks);
			}
		}).keySet()));
		return true;
	}

	/**
	 * 插入单条数据
	 * 
	 * @param data
	 *            数据
	 * @param isReplace
	 *            是否替换插入replace into
	 * @return
	 */
	public int insert(JsMap data, boolean isReplace) {
		if (data == null || data.isEmpty() || StringUtils.isBlank(this.options.getTable())) {
			logger.warn("[dao insert error] data isEmpty || table isEmpty");
			return 0;
		}
		Set<String> fieldSet = data.keySet();
		String sql = this.pInsertSql(fieldSet, isReplace);
		this.pDebugLog("[dao insert sql] " + sql);
		// 解析data中是否包含主键，并设置where
		// 如果开启replace插入，参数中没设置主键则不操作数据库
		if (isReplace && !this.pParseDataSetPKWhere(data)) {
			logger.warn("[dao replace fail] data no pks exists");
			return 0;
		}
		return getDb().getMaster().update(sql, data);
	}

	public int insert(JsMap data) {
		return this.insert(data, false);
	}

	/**
	 * 更新数据
	 * 
	 * @param data
	 * @return
	 */
	public int update(JsMap data) {
		this.pParseDataSetPKWhere(data);
		// 如果没有where条件则不操作数据库
		if (data == null || data.isEmpty() || this.options.getWhere() == null 
				|| this.options.getWhere().isEmpty()) {
			logger.warn("[dao update fail] data or where is empty");
			return 0;
		}
		// 过滤主键，主键无须更新数据库
		Map<String, Object> fields = Maps.filter(data, new Filter<String>() {
			public boolean apply(String t) {
				return !JStringUtils.equalsAny(t, pks);
			}
		});
		Options options = getOptions();
		String sql = getDb().getDialect().parseUpdateSql(options.getTable(), fields,
				options.getWhere());
		afterParseSql();
		this.pDebugLog("[dao update sql] " + sql);
		return getDb().getMaster().update(sql, data);
	}
	
	/**
	 * 删除数据
	 * !warn：此方法是硬删除数据库，建议使用软删除（更新del_flag标识）！
	 * @param data
	 * @return
	 */
	public int delete(JsMap data) {
		this.pParseDataSetPKWhere(data);
		// 如果没有where条件则不操作数据库
		if (data == null || data.isEmpty() || this.options.getWhere() == null 
				|| this.options.getWhere().isEmpty()) {
			logger.warn("[dao delete fail] data or where is empty");
			return 0;
		}
		Options options = getOptions();
		String sql = getDb().getDialect().parseDeleteSql(options.getTable(), options.getWhere());
		afterParseSql();
		this.pDebugLog("[dao delete sql] " + sql);
		return getDb().getMaster().update(sql, data);
	}
	
}
